package erfgame.core.world.terrain;

import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;


import erfgame.core.graph.simple.Node;
import erfgame.core.world.terrain.insertion.DefaultSurfaceInsertionComparer;
import erfgame.core.world.terrain.insertion.SurfaceInsertion;
import erfgame.core.world.terrain.insertion.SurfaceInsertionComparer;
import erfgame.core.world.terrain.insertion.SurfaceTerrainImageInsertion;

public final class BlockSurfaceImages {
	public static final int MAX_SURFACES = 255;
	
	private int nextSurfaceId;
	private int[][] surfaces;
	private PixelDepth[][] pixelDepths;
	
	private List<Node<SurfaceInsertion>> surfaceInsertions;
	
	public BlockSurfaceImages() {
		this.surfaceInsertions = new ArrayList<Node<SurfaceInsertion>>(4);
		this.nextSurfaceId = 1;
	}
	
	public BlockSurfaceImages( 
			int nextSurfaceId, 
			List<Node<SurfaceInsertion>> surfaceInsertions, 
			PixelDepth[][] pixelDepths, 
			int[][] surfaces 
	) {
		this.nextSurfaceId = nextSurfaceId;
		this.surfaces = surfaces;
		this.pixelDepths = pixelDepths;
		this.surfaceInsertions = surfaceInsertions;
	}

	public int getNextSurfaceId() {
		return nextSurfaceId;
	}

	public void setNextSurfaceId(int nextSurfaceId) {
		this.nextSurfaceId = nextSurfaceId;
	}

	public List<Node<SurfaceInsertion>> getSurfaceInsertions() {
		return surfaceInsertions;
	}

	public void setSurfaceImages(SurfaceImage[] surfaceImages) {
		this.surfaceInsertions = new ArrayList<Node<SurfaceInsertion>>( surfaceImages.length );
		for( int i=surfaceImages.length; i>0; ) {
			i--;
			SurfaceImage surfaceImage = surfaceImages[i];
			SurfaceTerrainImageInsertion imageRenderer;
			imageRenderer = new SurfaceTerrainImageInsertion();
			imageRenderer.setSurface(this, surfaceImage);
			// these will always be in the right order and have no dependencies
			surfaceInsertions.add( 
					new Node<SurfaceInsertion>(
							imageRenderer
					)
			);
		}
	}
	
	public void addInsertion( SurfaceInsertion insertion ) {
		addInsertionNoSort( insertion );
		// TODO attempt to insert in the existing order before sorting?
		sortRenderers();
	}
	
	public void addInsertions( List<SurfaceInsertion> insertions ) {
		addInsertionsNoSort(insertions);
		// TODO attempt to insert in the existing order before sorting?
		sortRenderers();
	}
	
	public void clearTemporaryInsertions() {
		for( int i=this.surfaceInsertions.size(); i>0; ) {
			i--;
			Node<SurfaceInsertion> node = surfaceInsertions.get( i );
			SurfaceInsertion insertion = node.getValue();
			if( !(insertion instanceof SurfaceTerrainImageInsertion ) ) {
				this.surfaceInsertions.remove( i );
			}
		}
	}
	
	public void addInsertionsNoSort( List<SurfaceInsertion> insertions ) {
		for( int i=insertions.size(); i>0; ) {
			i--;
			SurfaceInsertion insertion = insertions.get( i );
			addInsertionNoSort( insertion );
		}
	}
	
	public void addInsertionNoSort( SurfaceInsertion insertion ) {
		SurfaceInsertionComparer comparer = DefaultSurfaceInsertionComparer.INSTANCE;
		Node<SurfaceInsertion> newNode = new Node<SurfaceInsertion>( insertion );
		// work out where it sits in the existing tree
		for( int i=this.surfaceInsertions.size(); i>0; ) {
			i--;
			Node<SurfaceInsertion> node = this.surfaceInsertions.get( i );
			int ordering = comparer.compare(node.getValue(), insertion );
			if( ordering > 0 ) {
				// new node is in front
				newNode.addConnection( node );
			} else if( ordering < 0 ) {
				// new node is behind
				node.addConnection( newNode );
			}
		}
		this.surfaceInsertions.add( newNode );
	}
	
	public void sortRenderers() {
		ArrayList<Node<SurfaceInsertion>> sorted = new ArrayList<Node<SurfaceInsertion>>( this.surfaceInsertions.size() );
		// don't reverse the loop, we assume the list is already somewhat sorted (things with no dependencies first), 
		// which will make this quicker to do this way...I think that's right
		for( int i=0; i<this.surfaceInsertions.size(); i++ ) {
			Node<SurfaceInsertion> renderer = this.surfaceInsertions.get( i );
			// TODO : this should be correct, but there is an issue with the incoming connection count somewhere?!
			//if( renderer.getIncomingConnections() == 0 ) 
			{
				sortRenderers( renderer, sorted );
			}
		}
		
		// check for duplicates, suspect we've got problems with this
		for( int i=0; i<sorted.size(); i++ ) {
			Node<SurfaceInsertion> ii = sorted.get( i );
			for( int j=0; j<i; j++ ) {
				Node<SurfaceInsertion> ji = sorted.get( j );
				if( ji.equals( ii ) ) {
					System.out.println( "duplicate!!" );
				}
			}
		}
		
		if( this.surfaceInsertions.size() != sorted.size() ) {
			System.out.println( "("+this.surfaceInsertions.size()+"!="+sorted.size()+") dropped some images!" );
		}
		this.surfaceInsertions = sorted;
	}
	
	private void sortRenderers( Node<SurfaceInsertion> renderer, ArrayList<Node<SurfaceInsertion>> sorted ) {
		if( !sorted.contains( renderer ) ) {
			Collection<Node<SurfaceInsertion>> renderersInFront = renderer.getConnections();
			for( Node<SurfaceInsertion> rendererInFront : renderersInFront ) {
				if( rendererInFront.equals( renderer ) ) {
					System.out.println( "circular reference! "+renderer.getValue().getRendererChain().getHead().getEntity() );
				}
				sortRenderers( rendererInFront, sorted );
			}
			sorted.add( renderer );
		}		
	}
	
	public void removeRenderer( SurfaceInsertion renderer ) {
		// remove the node with that renderer
		Node<SurfaceInsertion> removedNode = null;
		for( int i=this.surfaceInsertions.size(); i>0; ) {
			i--;
			Node<SurfaceInsertion> node = this.surfaceInsertions.get( i );
			if( node.getValue() == renderer ) {
				removedNode = node;
				this.surfaceInsertions.remove( i );
				break;
			}
		}
		// remove that renderer from every child node
		if( removedNode != null ) {
			for( int i=this.surfaceInsertions.size(); i>0; ) {
				i--;
				Node<SurfaceInsertion> node = this.surfaceInsertions.get( i );
				node.removeConnection( removedNode );
			}
		}
		// don't need to reorder
	}
	
	public void paint( Graphics g, boolean isTop ) {
//		System.out.println( "---" );
		// TODO : reverse!!
		//for( int i=this.renderers.size(); i>0; ) {
		for( int i=0; i<this.surfaceInsertions.size(); i++ ) {
			Node<SurfaceInsertion> node = this.surfaceInsertions.get( i );
			//System.out.println( node.getValue() +"/"+node.getIncomingConnections() );
			SurfaceInsertion insertion = node.getValue(); 
			insertion.render( g, isTop );
		}
//		g.setColor( Color.red );
//		g.drawString( "("+g.getClipBounds().x+","+g.getClipBounds().y+")", 0, g.getFontMetrics().getHeight() );
	}
	
	public int[][] getSurfaces() {
		return surfaces;
	}

	public void setSurfaces(int[][] surfaces) {
		this.surfaces = surfaces;
	}

	public PixelDepth[][] getPixelDepths() {
		return pixelDepths;
	}

	public void setPixelDepths(PixelDepth[][] pixelDepths) {
		this.pixelDepths = pixelDepths;
	}
}
